アナログCPU:5108843109

ゲームと音楽とプログラミング(酒と女とロックンロールのノリで)

('ω') < イザユケエンジニャー

FIND_IN_SETでカンマ区切りの文字列から簡単検索

以下のような testtable テーブルについて、
カンマ区切りになっている文字列 strings を検索したい、と思うケースがあります。
あくまでカンマで区切ったひとつひとつをデータとして扱い、例えば「a」ではヒットさせたくないけど「abc」でヒットさせたい、という感じですね。

id strings
1 1,2,abc,def
2 1,3,abc,ghi,
3 ,,,

検索が発生する時点で既にこの設計は破綻しているのですが、
現実問題として「じゃあテーブル分けましょう!」と簡単にいかないことも多いので

そういうときは、MySQLであれば以下の関数を用いて検索できます。

FIND_IN_SET(検索する値, 検索対象の文字列・カラム);

主な挙動はこんな感じ。

-- SELECT * FROM `testtable` 以下
WHERE FIND_IN_SET('1'  , `strings`); -- id:1, id:2 がヒット
WHERE FIND_IN_SET(1    , `strings`); -- id:1, id:2 がヒット(数字は数値でもOK!)
WHERE FIND_IN_SET(2    , `strings`); -- id:1 のみヒット
WHERE FIND_IN_SET('abc', `strings`); -- id:1, id:2 がヒット
WHERE FIND_IN_SET('a'  , `strings`); -- ヒットしない
WHERE FIND_IN_SET('def', `strings`); -- id:1のみヒット
WHERE FIND_IN_SET(''   , `strings`); -- id:2, id:3 がヒット
WHERE FIND_IN_SET(','  , `strings`); -- ヒットしない

うわー便利。

AND検索・OR検索はいつも通り。

WHERE FIND_IN_SET('abc', `strings`) AND FIND_IN_SET('def', `strings`); -- id:1 のみヒット
WHERE FIND_IN_SET('def', `strings`) OR  FIND_IN_SET('ghi', `strings`); -- id:1, id:2 がヒット

MySQL独自関数はちょっと…という場合は前後にカンマを付けてLIKE検索。
と言いつつこの書き方もMySQL独自なので、文字列連結方法は各環境に応じてもにょもにょしましょう。
アプリ側でINSERT時に前後にカンマ付けるようにしておいても良いですけどね。

-- 「CONCAT(',', `strings`, ',')」でstringsカラムの前後にカンマを付けた文字列になる
-- 「LIKE '%,abc,%'」で「,abc,」の部分一致検索
SELECT * FROM `testtable` WHERE CONCAT(',', `strings`, ',') LIKE '%,abc,%';

雑だけど挙動はだいたい同じです。

-- SELECT * FROM `testtable` 以下
WHERE CONCAT(',', `strings`, ',') LIKE '%,1,%'  ; -- id:1, id:2 がヒット
WHERE CONCAT(',', `strings`, ',') LIKE '%,2,%'  ; -- id:1 のみヒット
WHERE CONCAT(',', `strings`, ',') LIKE '%,abc,%'; -- id:1, id:2 がヒット
WHERE CONCAT(',', `strings`, ',') LIKE '%,a,%'  ; -- ヒットしない
WHERE CONCAT(',', `strings`, ',') LIKE '%,def,%'; -- id:1のみヒット
WHERE CONCAT(',', `strings`, ',') LIKE '%,,%'   ; -- id:2, id:3 がヒット
WHERE CONCAT(',', `strings`, ',') LIKE '%,,,%'  ; -- id:3のみヒット(ここが違う)

ただしいずれにしろインデックスは効きません。効くわけない。
レコード数や実行頻度と相談して、ご利用は計画的に。
テーブルを分けることができないならフリーワード検索を応用した方がまだ良いかと思います。
(こちらはFULLTEXTインデックスを設定してればちゃんと効く)

連想配列で検索したい!!!

(2018-09-13:書いてるとおりには動いてたけど、実用を考えると破綻してたので、まるっと書き直しました)

array_columnを使えないバージョンを使わざるを得ない時用。
使えるならこちらをご参照ください。

今回は以下のような連想配列から検索することを考えます。

<?php
$data_list = array(
    1 => array('hoge' => "a"  , 'fuga' => "A"  ),
    2 => array('hoge' => "b"  , 'fuga' => "B"  ),
    3 => array('hoge' => "a"  , 'fuga' => "C"  ), // hogeが1と重複していることに注意
    4 => array('hoge' => null , 'fuga' => "D"  ),
    5 => array('hoge' => false, 'fuga' => "E"  ),
    6 => array('hoge' => "f"  , 'fuga' => null ),
    7 => array('hoge' => "g"  , 'fuga' => false),
);

検索パターンは次の3通り。

  • キーからhogeの値を求める
  • hogeからキーの値を求める
  • hogeからfugaの値を求める

これを全部まとめて関数化したい!と思うのですが、一つ問題が。

「検索結果が存在しないこと」と「検索結果がfalseであること」の戻り値を両立するの難しくない?

上記の配列で、「キーが7のfugaの値が欲しい!」となったとき、欲しい結果はもちろんfalseなんですが、
じゃあ「キーが8のfugaの値が欲しい!」と言われたら? 8は存在しないんだから、関数としてはfalseを返すべきじゃないの?

まあ、戻り値をこんな感じにすることもできますが

array(
    'not_found_flag' => 1,    // 見つからなかったよフラグ
    'result'         => null, // 検索結果
)

お手軽に検索したいから関数化するというのに、戻り値がいちいち配列なのはちょっとつらい。あとなんかかっこわるい。

連想配列の中身を検索し、キーを返す関数

ということで、そもそもひとつにまとめないパターンを考えてみました。
キーはfalseにならないのでこれなら関数としてもすっきり。

ただし、ここでは「hoge」やら「fuga」やらが存在してないケースまでは考慮してないので
必要に応じて更にバリデートを入れることになります。

あと、比較は厳密に行っています。場合によってはゆるい比較にしてもOK。

<?php
function getKey($list, $val, $key)
{
    foreach ($list as $k => $v)
    {
        if ($v[$key] === $val)
        {
            return $k;
        }
    }
    return false;
}

さっきの配列で検索してみるとこんな感じ。

<?php
var_dump( getKey($data_list, "a"  , "hoge") ); // 1 : 重複してると最初のキーを返す
var_dump( getKey($data_list, null , "hoge") ); // 4 : 値がnullの場合もOK
var_dump( getKey($data_list, false, "hoge") ); // 5 : 値がfalseの場合もOK
var_dump( getKey($data_list, "h"  , "hoge") ); // false : 存在しない
var_dump( getKey($data_list, "A"  , "fuga") ); // 1 : もちろんfugaからも検索可

あとは適宜必要に応じて戻り値がfalseかどうかをチェックすればOK。
検索結果が存在しないということはあり得ない、という場合は見なくていいし。

ということで実際に検索してみましょう。

さっきの検索したいパターンに当てはめると、

1. キーからhogeの値を求める

これはキーが分かっている状態なのでそもそもさっきの関数すら使わない。

<?php
// キー「1」の「hoge」を求める:$resは「a」
$res = $data_list[1]['hoge'];

レコードが存在しない可能性があるならこう。

<?php
if (isset($data_list[1]['hoge']))
{
    // 検索結果は $data_list[1]['hoge']
}
else 
{
    // 検索結果なし
}
2. hogeからキーの値を求める

これはつまりさっきの関数の戻り値。

<?php
// hogeが「a」のキーを求める:$resは「1」
$res = getKey($data_list, "a"  , "hoge");

レコードが存在しない可能性があるならこう。

<?php
$res = getKey($data_list, "a"  , "hoge");
if ($res !== false)
{
    // 検索結果は $res
}
else 
{
    // 検索結果なし
}
3. hogeからfugaの値を求める

1と2の合わせ技。

<?php
// hogeが「a」の「fuga」を求める:$resは「A」
$res = $data_list[getKey($data_list, "a"  , "hoge")]['fuga'];

レコードが存在しない可能性があるならこう。

<?php
$key = getKey($data_list, "a"  , "hoge");
if ($key !== false)
{
    // 検索結果は $data_list[$key]['fuga'];
}
else
{
    // 検索結果なし
}

falseのデータとかそもそもレコードが存在しないとかありえないから一発で済ませたい!!!

という場合はこちら。

キー「1」を元に、hogeの値「a」を求める

さっきと同じ。

<?php
// $resは「a」
$res = $data_list[1]['hoge'];
hogeの値「a」を元に、キーの値「1」を求める

ほい。

<?php
// $resは「1」
$res = key(array_filter(array_map(function($l){return $l['hoge'] === "a";}, $data_list)));

いきなりややこしくなりました。
ちょっと分解しましょう。

<?php
// 配列[hoge]が「a」ならtrueを返す無名関数
function($l){return $l['hoge'] === "a";}

// …をarray_mapにぶちこむ
array_map(function($l){return $l['hoge'] === "a";}, $data_list);

// その結果はtrueかfalseでできた一次元配列
//   array(1 => true, 2=> false, 3=> true)
// になるので、それをtrueでフィルタリングする
array_filter(array_map(function($l){return $l['hoge'] === "a";}, $data_list));

// trueだけになった配列
//   array(1 => true, 3=> true)
// の、現在の…っつーか何もしてないのでつまりは先頭のキーを取得
key(array_filter(array_map(function($l){return $l['hoge'] === "a";}, $data_list)));

// 完成
$res = key(array_filter(array_map(function($l){return $l['hoge'] === "a";}, $data_list)));

ちなみに存在しない値で検索しようとした場合はNULLになります。
ただ、キーがNULLになることはあり得ないので(NULLに設定しようとすると勝手に空文字になります)
NULLなら検索結果なしと判断してOK。

hogeの値「a」を元に、fugaの値「A」を求める

上記2つの組み合わせ。

<?php
// $resは「A」
$res = $data_list[key(array_filter(array_map(function($l){return $l['hoge'] === "a";}, $data_list)))]['fuga'];

これも存在しない場合はNoticeが出ます。

背景色に応じた文字色に変える / ランダム色を生成する

まずはサンプルから。
押すたびに背景色がランダムで変わり、文字色が白か黒の適した色に変わります。



ここをクリック


コードはこんな感じ。

<div id="colordiv" onclick="changeColor();">
  ここをクリック
</div>

<script>
/* 与えられた背景色に適した文字色を返す */
function getStrColor (bg){
    // 色をRGBに分割
    var r = parseInt(bg.substr(1, 2), 16);
    var g = parseInt(bg.substr(3, 2), 16);
    var b = parseInt(bg.substr(5, 2), 16);

    // 適した色を返す
    return ((((r * 299) + (g * 587) + (b * 114)) / 1000 ) < 128) ? "#FFFFFF" : "#000000" ;
}

/* ランダムな背景色とそれに適した文字色を求め、それを設定する */
function changeColor(){
    // ランダムな色を生成(背景色)
    var bgcolor = "#" + Math.round(Math.random() * 255).toString(16) + Math.round(Math.random() * 255).toString(16) + Math.round(Math.random() * 255).toString(16);
    // 生成した背景色に適した文字色を求める
    var strcolor = getStrColor(bgcolor);
    
    // 設定する
    $("#colordiv").css("background", bgcolor);
    $("#colordiv").css("color", strcolor);
}
</script>

ほんとはCSSだけで文字色求める方法を探してたのですが無理そうな気がする。

複数の文字列データをカンマ区切りにするけど5データごとに改行する

関数化した。

<?php
function str_chunk($list, $size)
{
    return implode("\n", array_map(function($l){return implode(",", $l);}, array_chunk($list, $size)));
}

…。

読みにくいのは置いといて使い方と結果はこんな感じ。

<?php
$data_list = array("a","b","c","d","e","f","g","h","j","i","k","l","m",);
echo str_chunk($data_list, 5);
a,b,c,d,e
f,g,h,j,i
k,l,m

やったね。めでたしめでたし。

念のため、カンマと改行文字は引数に渡せるようにしてみる

<?
function str_chunk($list, $size, $del1 = ",", $del2 = "\n")
{
    return implode($del2, array_map(function($l, $d){return implode($d, $l);}, array_chunk($list, $size), array_fill(0, count(array_chunk($list, $size)), $del1)));
}

さすがにアホではないだろうか?
動いたけど。

array_mapは便利だけど、array(1,2,3)のそれぞれに2を掛けたい、というときに
array(1,2,3)とarray(2,2,2)とかいう無駄引数を渡さなきゃいけない。
(そのための array_fill(0, count(array_chunk($list, $size)), $del1) というクソ表記)

じゃあもうarray_mapとか投げ捨ててforeachでよくない?

<?php
function str_chunk($list, $size, $del1 = ",", $del2 = "\n")
{
    $chunk_list = array_chunk($list, $size);

    $str_list = array();
    foreach ($chunk_list as $k => $v)
    {
        $str_list[] = implode($del1, $v);
    }

    return implode($del2, $str_list);
}

読みやすくなった。

ついでに速度検証

array_map版(区切り文字指定できる方)とforeach版の速度比較。
データはサンプルコードにあるa~mの13文字、
10万回実行を5セットした平均値。

array_map版:296.5834
foreach版:269.9892

単位はミリ秒。
まあ誤差レベルだけどarray_mapの方が遅い。

div要素をきれいに並べるのは難しい(floatを使って配置)


これのfloat版。

今回も以下の3つを並べてみます。

<div class="div1">div要素1</div>
<div class="div2">div要素2</div>
<div class="div3">div要素3</div>


縦一列に並べる(何もしない)

前回と同じなので省略。そのままでいい。

横一列に並べる

全部に「float: left;」を設定。

<style>

.div1{
    float: left;
}
.div2{
    float: left;
}
.div3{
    float: left;
}
</style>

div要素1
div要素2
div要素3

これの問題点は、そのままにしておくとその下の要素にも影響すること。
(重なって表示されるとか、その次の要素も回り込んで表示されるとか)

横並びにした次の要素に「clear: left;」を設定すると回避できます。
↑のサンプルの直後には

<div style="clear: left;"></div>

と空divを入れています。

セル結合したテーブルっぽい感じのやつ(その1)

ということで、今回はdiv2とdiv3に「float: left;」を設定。
displayでなんとかする方式と違って、widthをしっかり入れてやらないと崩れます。

<style>
.div1{
    width: 10em;
}
.div2{
    width: 5em;
    float: left;
}
.div3{
    width: 5em;
    float: left;
}
</style>

div要素1
div要素2
div要素3

セル結合したテーブルっぽい感じのやつ(その2)

こちらはdiv2とdiv3を親divでくくり、div1と親divを横並びにします。

<div class="div1">div要素1</div>
<div class="div_parent">
  <div class="div2">div要素2</div>
  <div class="div3">div要素3</div>
</div>
<style>
.div_parent{
    float: left;
}
.div1{
    height: 10em;
    float: left;
}
.div2{
    height: 5em;
}
.div3{
    height: 5em;
}
</style>

↓またズレた…。はてブロのCSSがなんか影響してるっぽい。

div要素1

div要素2
div要素3

div要素をきれいに並べるのは難しい(displayを使って配置)

いろんな方法はあるっぽいけど、とりあえず調べて動かしてみたやつを残しておく。
ブラウザによっては動かないとかあるかも。

displayでなくfloatでやるときはこっち。


以下の3つのdivをいろんな並べ方してみます。
(3つと言いつつ、パターンによって親divが発生したりします)

<div class="div1">div要素1</div>
<div class="div2">div要素2</div>
<div class="div3">div要素3</div>

長くなるので、colorやらbackgroundやらその他諸々は省略しています。

縦一列に並べる(何もしない)

<style>
.div1{
    width: 5em;
}
.div2{
    width: 5em;
}
.div3{
    width: 5em;
}
</style>

とりあえずwidthが揃っていればきれいなんじゃないですかね(なげやり)

div要素1
div要素2
div要素3

横一列に並べる

まず親divを作ります。

<div class="div_parent">
  <div class="div1">div要素1</div>
  <div class="div2">div要素2</div>
  <div class="div3">div要素3</div>
</div>

そして親divに「display: table;」、
横一列したい子divに「display: table-cell;」を追加。
heightは長い要素に合わせて勝手に調節してくれてるっぽい。

<style>
.div_parent{
    display: table;
}
.div1{
    display: table-cell;
}
.div2{
    display: table-cell;
}
.div3{
    display: table-cell;
}
</style>


div要素1
div要素2
div要素3

セル結合したテーブルっぽい感じのやつ(その1)

(語彙力がない)

さっきのを応用して…

<div class="div1">div要素1</div>
<div class="div_parent">
  <div class="div2">div要素2</div>
  <div class="div3">div要素3</div>
</div>
<style>
.div_parent{
    display: table;
}
.div1{
    width: 10em;
}
.div2{
    width: 5em;
    display: table-cell;
}
.div3{
    width: 5em;
    display: table-cell;
}
</style>

div要素1

div要素2
div要素3

セル結合したテーブルっぽい感じのやつ(その2)

さっきのを反時計回りに90度傾けた感じのやつ。

<div class="div_parent">
  <div class="div1">div要素1</div>
  <div class="div_child">
    <div class="div2">div要素2</div>
    <div class="div3">div要素3</div>
  </div>
</div>
<style>
.div_parent{
    display: table;
}
.div_div_child{
    display: table-cell;
}
.div1{
    display: table-cell;
}
.div2{
}
.div3{
}
</style>

↓なんかめっちゃズレてるな…ローカルだときれいに並んでるんだけども…。(このブログ自体のCSSが影響してるっぽい)


div要素1

div要素2
div要素3

jQueryで属性とか値とかを書き換える&取得するいろいろ

value

<input id="sample1" type="hidden" value="">

<script>
// value値を「test」に書き換える
$("#sample1").val("test");

// value値を取得する(↑で書き換えているので「test」となる)
console.log( $("#sample1").val() );
<script>

テキスト

<span id="sample2">sample2</span>

<script>
// テキストを「sample2_update」に書き換える
$("#sample2").text("sample2_update");

// テキストを取得する(↑で書き換えているので「sample2_update」となる)
console.log( $("#sample2").text() );
<script>

スタイル

(クラスの追加削除で対応した方がよいと思いますが…)

<span id="sample3">sample3</span>

<script>
// CSSに「color: #F00;」を追加する
$("#sample3").css("color", "#F00");
//$("#sample3").attr("style", "color: #F00;"); // こっちの方法でもOK

// style属性を取得する
console.log( $("#sample3").attr("style") );
<script>

チェックボックス

<input id="sample4" type="checkbox">

<script>
// チェックをONにする(外したい場合はfalseにする)
$("#sample4").prop("checked", true);

// チェック有無を取得する(true/false)
console.log( $("#sample4").prop("checked") );
<script>

ラジオボタン

<input name="sample5" type="radio" value="1">
<input name="sample5" type="radio" value="2">

<script>
// valueが1であるものを選択状態にする
$("[name=sample5]").val(["1"]);

// 選択されているもののvalue値を取得する
console.log( $("[name=sample5]").val() );
<script>

セレクトボックス

<select id="sample6">
  <option value="0">---</option>
  <option value="1">aaa</option>
</select>

<script>
// valueが1であるものを選択状態にする
$("#sample6").val("1");

// 選択されているもののvalue値を取得する
console.log( $("#sample6").val() );
<script>

jQueryでclassをもちゃもちゃする

基本編(add, remove, toggle, has)

追加
$(要素).addClass(class名);
削除
$(要素).removeClass(class名);
あれば削除、なければ追加
$(要素).toggleClass(class名);
あるかどうかを確認
// あればtrue、なければfalse
$(要素).hasClass(class名);
実装サンプル







<style>
#testdiv {
    padding: 5px;
}
.back-red{
    background: #F00;
}
</style>
<div id="testdiv" class="">
  <input type="button" onclick="addBackRed();" value="背景を赤くする(add)">
  <input type="button" onclick="removeBackRed();" value="背景を赤くするのをやめる(remove)">
  <input type="button" onclick="toggleBackRed();" value="背景の色を切り替える(toggle)">
  <input type="button" onclick="hasBackRed();" value="背景の色が赤いかどうか確認する(has)">
</div>
<script>
function addBackRed(){
    $("#testdiv").addClass("back-red");
}
function removeBackRed(){
    $("#testdiv").removeClass("back-red");
}
function toggleBackRed(){
    $("#testdiv").toggleClass("back-red");
}
function hasBackRed(){
    if ($("#testdiv").hasClass("back-red")){
        alert("赤いです");
    }
    else{
        alert("赤くないです");
    }
}
</script>

応用編?

基本的に、addとremoveとtoggleとhasさえあればなんとかなりそうなので、あとはその組み合わせ。
というか厄介なのはhas。

「複数のclassをすべて持っているかどうか確認したい」
「複数のclassのどれかを持っているかどうか確認したい」
「複数のclassがすべて一致する(過不足がない)かどうか確認したい」
という時のための関数を作ったので置いとく。

複数のclassをすべて持っているかどうか確認したい
function hasAllClass(obj, c){
    var c_list = c.split(" ");
    var retflg = true;
    $(c_list).each(function(i, e){
        if (!obj.hasClass(e)){
            retflg = false;
        }
    });
    return retflg;
}

使用例

<span id="testspan" class="a b c">にゃーん</span>
<script>
  console.log( hasAllClass($("#testspan"), "a b c") ); // true(完全に一致してるのでOK)
  console.log( hasAllClass($("#testspan"), "b c a") ); // true(順番が変わってもOK)
  console.log( hasAllClass($("#testspan"), "a b"  ) ); // true(足りないのはOK)
  console.log( hasAllClass($("#testspan"), "a b d") ); // false(余分なのはNG)
</script>
複数のclassのどれかを持っているかどうか確認したい
function hasEitherClass(obj, c){
    var c_list = c.split(" ");
    var retflg = false;
    $(c_list).each(function(i, e){
        if (obj.hasClass(e)){
            retflg = true;
            return false;
        }
    });
    return retflg;
}

使用例

<span id="testspan" class="a b c">にゃーん</span>
<script>
  console.log( hasEitherClass($("#testspan"), "a b c") ); // true(完全に一致してるのでOK)
  console.log( hasEitherClass($("#testspan"), "b a"  ) ); // true(順番違いや足りないのはOK)
  console.log( hasEitherClass($("#testspan"), "a d"  ) ); // true(存在しないものが混じっててもOK)
  console.log( hasEitherClass($("#testspan"), "d e"  ) ); // false(存在しないものしかないのはNG)
</script>
複数のclassがすべて一致する(過不足がない)かどうか確認したい
function hasOnlyClass(obj, c){
    var cmp_c_list = c.split(" ").sort();
    var tag_c_list = obj.attr("class").split(" ").sort();
    return cmp_c_list.toString() == tag_c_list.toString();
}

使用例

<span id="testspan" class="a b c">にゃーん</span>
<script>
  console.log( hasOnlyClass($("#testspan"), "a b c")   ); // true(完全に一致してるのでOK)
  console.log( hasOnlyClass($("#testspan"), "b a c")   ); // true(順番が変わってもOK!)
  console.log( hasOnlyClass($("#testspan"), "a b"  )   ); // false(足りないのでNG)
  console.log( hasOnlyClass($("#testspan"), "a b c d") ); // false(余分なのでNG)
</script>

FireFoxでJSをワンクリックで動かせるようにする

ブックマークのURL部分にJSを仕込むだけなので、
「普通のブックマークをアドレスバー付近からワンクリックでアクセスできるようにする」でもある。
もっと言えば「ブックマークツールバーを使う」でしかない。

手順1.目的のJSをブックマークに登録する

Ctrl+Bとかでブックマークを開き、
「ブックマークツールバー」を右クリックで「新しいブックマーク」します。
適当な名前を付けて、URLのところにJSを仕込みます。
保存すると、まずは普通のブックマークになりました。

ブックマークを既に作ってある場合は
「ブックマークツールバー」へドラッグ&ドロップしてください。

手順2.ブックマークツールバーの中身を表示する

アドレスバーの「カスタマイズ」で、
「ブックマークツールバーの項目」を好きなところに設置します。

以上!

f:id:honey8823:20180907164512p:plain
こんな感じになります。

ちなみに

今回はこれを仕込むために設置しました。
君はHTML5の contentEditable 属性を知っているか | Tips Note by TAM

javascript:(function(b){b.contentEditable=!b.isContentEditable;})(document.body);

これで画面仕様書を作るのが楽になったぞ。